/*
 * $AIST_Release: 0.9.0 $
 * Copyright 2011 Information Technology Research Institute, National
 * Institute of Advanced Industrial Science and Technology
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *    http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package parser

import scala.util.parsing.input.Positional
import com.google.protobuf.Descriptors._

/**
 * The module defines "data type" data structures.
 */
object DataTypes {

  /**
   * Compile time data type.
   */
  sealed trait DataType extends TreeWalker.Node with Positional

  /**
   * Data type with name.
   */
  trait NamedType extends DataType {
    val name: String
    override def toString = name
  }
  object NamedType {
    def unapply(n: NamedType): Option[String] = Some(n.name)
  }
  
  case class ArrayType(contents: DataType) extends DataType {
    override def toString() = "array of " + contents.toString
  }
  case class MapType(keys: DataType, values: DataType) extends DataType {
    override def toString() = "map[%s] of %s".format(keys, values)
  }
  trait TupleType extends DataType {
    val members: List[(String, DataType)]
  }
  case class PureTupleType(override val members: List[(String, DataType)]) extends TupleType {
    override def toString() = "{%s}".format(members.map {case (name, dataType) => "%s: %s;".format(name, dataType)}.mkString(""));
  }
  case class EnumType(desc: EnumDescriptor) extends NamedType {
    override val name: String = desc.getName
  }
  case class MessageType(desc: Descriptor, override val members: List[(String, DataType)]) extends TupleType with NamedType {
    override val name: String = desc.getName
  }
  case class FunctionType(retType:DataType, argsType: List[DataType]) extends DataType {
    override def toString() = "function(%s): %s".format(argsType.mkString(", "), retType)
  }
  
  /**
   * Function type list.
   * This is for function overloading.
   */
  case class FunctionList(funcs: List[FunctionType]) extends DataType

  /**
   * Reference type.
   * This expresses assignable.
   */
  case class RefType(dataType: DataType) extends DataType

  /**
   * Strip reference attribute.
   */
  def stripRef(tgt:DataType) = tgt match {
    case RefType(t) => t
    case t          => t
  }

  /**
   * Resolves type reference.
   */
  def resolveType(typeMap: Map[Int, DataType], dataType: DataType): DataType = dataType match {
    case TypeReference(id) => resolveType(typeMap, typeMap(id))
    case _ => dataType
  }

  def sucRef(src:DataType, tgt:DataType) = src match {
    case RefType(_) => RefType(stripRef(tgt))
    case _          => stripRef(tgt)
  }

  case class ImperfectType(override val name: String) extends NamedType { 
    override def toString() = "ImperfectType(%s)".format(name) 
  }
  case class TypeReference(id: Int) extends DataType

  case class WildCardType(id: String) extends DataType

  case object UnkownType extends DataType

  trait BuildInDataType extends NamedType
  case object IntType         extends BuildInDataType { override val name = "int"   } 
  case object FloatType       extends BuildInDataType { override val name = "float" }
  case object TimeType        extends BuildInDataType { override val name = "time" }
  case object FingerPrintType extends BuildInDataType { override val name = "fingerprint" }
  case object StringType      extends BuildInDataType { override val name = "string" }
  case object BytesType       extends BuildInDataType { override val name = "bytes" }
  case object EmptyType       extends BuildInDataType { override val name = "empty" }

  private def makePair(t: NamedType) = (t.name, t)
  val buildInTypes = Map[String, NamedType](
    makePair(IntType),
    makePair(FloatType),
    makePair(TimeType),
    makePair(FingerPrintType),
    makePair(StringType),
    makePair(BytesType)
  )

  case class TableType(keyType: List[DataType],
                       varType: DataType, 
                       opts: List[(String, DataType)]) extends DataType

  private type TypeMap = Map[String, DataType]

  /**
   * Returns whether "targets" data type list matches "patterns" data type list.
   * This function considers wildcard type in "patterns".
   */
  def matchTypeList(typeRefMap: Map[Int, DataType], patterns: List[DataType], targets: List[DataType]): Boolean = {
    matchTypeListImpl(typeRefMap, Map(), patterns, targets)._2
  }

  private def matchTypeListImpl(typeRefMap: Map[Int, DataType], m: TypeMap, patterns: List[DataType], targets: List[DataType]): (TypeMap, Boolean) = {
    def f(m: TypeMap, patterns: List[DataType], targets: List[DataType]): (TypeMap, Boolean) = {
      (patterns, targets) match {
        case (p::ps, t::ts) => 
          matchType(typeRefMap, m, p, t) match {
            case (m1, true) => f(m1, ps, ts)
            case _          => (m, false)
          }
        case _ => (m, true)
      }
    } 
    if (patterns.length == targets.length) {
      f(m, patterns, targets)
    } else {
      (Map(), false)
    }
  }

  /**
   * Compares type.
   *
   * This function ignores reference information.
   *
   * @param typeRefMap Type reference table.
   * @param pattern    
   * @param target     
   *
   * @return True if "target" matches "pattern".
   */
  def matchType(typeRefMap: Map[Int, DataType], pattern: DataType, target: DataType): Boolean = matchType(typeRefMap, Map(), pattern, target)._2

  private def matchType(typeRefMap: Map[Int, DataType], m: TypeMap, pattern: DataType, target: DataType): (TypeMap, Boolean) = { 
    type Members = List[(String, DataType)]

    def matchTuple(m: TypeMap, pmems: Members, tmem: Members): (TypeMap, Boolean) = {
      (pmems, tmem) match {
        case ((pname, ptype)::ps, (tname, ttype)::ts) if (pname == tname) => 
          matchType(typeRefMap, m, ptype, ttype) match {
            case (m1, true) => matchTuple(m1, ps, ts)
            case x          => x
          }
        case (Nil, Nil) => (m, true)
        case _ => (m, false)
      }
    }

    val realPat = resolveType(typeRefMap, stripRef(pattern))
    val realTgt = resolveType(typeRefMap, stripRef(target))

    assert(!realPat.isInstanceOf[ImperfectType])
    assert(!realPat.isInstanceOf[TypeReference])
    assert(!realPat.isInstanceOf[RefType])
    assert(!realPat.isInstanceOf[FunctionList])
    assert(realPat != UnkownType)

    assert(!realTgt.isInstanceOf[ImperfectType])
    assert(!realTgt.isInstanceOf[TypeReference])
    assert(!realTgt.isInstanceOf[RefType])
    assert(!realTgt.isInstanceOf[FunctionList])
    assert(realTgt != UnkownType)
    assert(!realTgt.isInstanceOf[WildCardType])

    (realPat, realTgt) match {
      case (WildCardType(id), x) => 
        if (m.contains(id)) matchType(typeRefMap, m, m(id), x)
        else                (m.updated(id, x), true)
      case (ArrayType(pc), ArrayType(tc)) => 
        matchType(typeRefMap, m, pc, tc)
      case (MapType(pk,pv), MapType(tk, tv)) => 
        matchType(typeRefMap, m, pk, tk) match {
          case (m1, true)  => matchType(typeRefMap, m1, pv, tv)
          case x => x
        }
      case (t1: TupleType, t2: TupleType) => // TODO: 要検討: Memberが同じならTupleとMessageは同じとして扱う
        matchTuple(m, t1.members, t2.members)
      case (f1: FunctionType, f2: FunctionType) =>
        matchTypeListImpl(typeRefMap, m, f1.argsType :+ f1.retType, f2.argsType :+ f2.retType)
      case (t1: EnumType, t2: EnumType) => (m, t1 == t2)
      case (t1: BuildInDataType, t2: BuildInDataType) => (m, t1 == t2)
      case _ => (m, false)
    }
  }

  /**
   * Gets name of data type.
   *
   * This function resolves type reference and returns data type name.
   * This function ignores reference information.
   *
   * @param typeRefMap Type reference table.
   * @param dataType   Gets name this data type.
   *
   * @return name of data type.
   */
  def getName(typeRefMap: Map[Int, DataType], dataType: DataType): String = {
    val real = resolveType(typeRefMap, stripRef(dataType))

    assert(!real.isInstanceOf[ImperfectType])
    assert(!real.isInstanceOf[TypeReference])
    assert(!real.isInstanceOf[RefType])
    assert(!real.isInstanceOf[FunctionList])
    assert(real != UnkownType)

    real match {
      case t: NamedType      => t.name
      case WildCardType(id)  => id
      case ArrayType(c)      => "array of " + getName(typeRefMap, c)
      case MapType(k,v)      => "map[%s] of %s".format(getName(typeRefMap,k), getName(typeRefMap, v))
      case PureTupleType(ms) => "{" + ms.map { case (n, t) => "%s: %s;".format(n, getName(typeRefMap, t))}.mkString("") + "}"
      case f: FunctionType   => "function(%s): %s".format(f.argsType.map { getName(typeRefMap, _) }, getName(typeRefMap, f.retType))
    }
  }
}
